package top.atcra.rpc.transport.utils;

import cn.hutool.core.date.DateTime;
import lombok.Getter;

import java.util.concurrent.atomic.LongAdder;

/**
 * 请求id的生成器
 *
 * @author atcra
 * @date 2025-3-23
 **/
public class RequestIdGenerator {

    // 雪花算法
    // 机房号（数据中心） 5bit
    // 机器号           5bit
    // 时间戳           42bit  原本64位表示的时间，必须减少，自由选择一个比较近的时间
    // 序列号           12bit  同一个机房的同一个机器号的同一个时间可以因为并发量很大需要多个id
    // 合计             5+5+42+12 = 64bit

    // 起始时间戳
    public static final long START_STAMP = DateTime.of("2025-01-01", "yyyy-mm-dd").getTime();
    // 指定各个字段的长度
    public static final long DATA_CENTER_BIT = 5L;
    public static final long MACHINE_BIT = 5L;
    public static final long SEQUENCE_BIT = 12L;

    // 最大值 Math.pow(2,5) -1  用-1的补码左移多少位再取反 就得到了2的多少次方  设置这些字段的最大值
    public static final long DATA_CENTER_MAX = ~(-1L << DATA_CENTER_BIT);
    public static final long MACHINE_MAX = ~(-1L << MACHINE_BIT);
    public static final long SEQUENCE_MAX = ~(-1L << SEQUENCE_BIT);


    // 时间戳 （42） 机房号 （5） 机器号 （5） 序列号 （12）
    // 101010101010101010101010101010101010101011 10101 10101 101011010101
    // 通过左移位然后在进行或运算，将这些位进行组合 形成雪花算法的id号
    public static final long TIMESTAMP_LEFT = DATA_CENTER_BIT + MACHINE_BIT + SEQUENCE_BIT;
    public static final long DATA_CENTER_LEFT = MACHINE_BIT + SEQUENCE_BIT;
    public static final long MACHINE_LEFT = SEQUENCE_BIT;

    @Getter
    private final long dataCenterId;
    @Getter
    private final long machineId;
    private final LongAdder sequenceId = new LongAdder();
    // 时钟回拨的问题，我们需要去处理  需要记录上一次的时间
    private long lastTimeStamp = -1L;

    public RequestIdGenerator(long dataCenterId, long machineId) {
        // 判断传入的参数是否合法
        if (dataCenterId > DATA_CENTER_MAX || machineId > MACHINE_MAX) {
            throw new IllegalArgumentException("数据中心编号或机器号需要介于0-31(含)之间");
        }
        this.dataCenterId = dataCenterId;
        this.machineId = machineId;
    }

    /**
     * 获取下个请求ID
     *
     * @return long
     */
    public synchronized long getId2() {
        // 第一步：处理时间戳的问题
        long currentTime = System.currentTimeMillis();

        long timeStamp = currentTime - START_STAMP;

        // 判断时钟回拨
        if (timeStamp < lastTimeStamp) {
            throw new RuntimeException("您的服务器进行了时钟回调.");
        }

        // sequenceId需要做一些处理，如果是同一个时间节点，必须自增
        if (timeStamp == lastTimeStamp) {
            sequenceId.increment();
            // 如果同一个时间点的序列号用光了，卡一下进入下一个时间戳
            if (sequenceId.sum() >= SEQUENCE_MAX) {
                timeStamp = getNextTimeStamp();
                sequenceId.reset();
            }
        } else {
            sequenceId.reset();
        }

        // 执行结束将时间戳赋值给lastTimeStamp
        lastTimeStamp = timeStamp;
        long sequence = sequenceId.sum();
        return timeStamp << TIMESTAMP_LEFT | dataCenterId << DATA_CENTER_LEFT
                | machineId << MACHINE_LEFT | sequence;

    }

    private long getNextTimeStamp() {
        // 获取当前的时间戳
        long current = System.currentTimeMillis() - START_STAMP;
        // 如果一样就一直循环，直到下一个时间戳
        while (current == lastTimeStamp) {
            current = System.currentTimeMillis() - START_STAMP;
        }
        return current;
    }

    public static void main(String[] args) {
        RequestIdGenerator idGenerator = new RequestIdGenerator(1, 2);
        for (int i = 0; i < 1000; i++) {
            new Thread(() -> System.out.println(idGenerator.getId2())).start();
        }
    }
}
